﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using BGShell.Interop;
using WindowsFormsAero;

namespace BGShell.Components
{
    public sealed class EditControlDataTip
    {
        private readonly Int32 _start;
        private readonly Int32 _length;
        private readonly DataTipItem _item;

        public EditControlDataTip(int start, int len, DataTipItem item)
        {
            _start = start;
            _length = len;
            _item = item;
        }

        public int Start
        {
            get { return _start; }
        }

        public int Length
        {
            get { return _length; }
        }

        public int End
        {
            get { return _start + _length; }
        }

        public DataTipItem Item
        {
            get { return _item; }
        }

        public bool Contains(int index)
        {
            return index >= Start && index <= End;
        }
    }

    public class EditControlDataTipProvider : IDisposable
    {
        private readonly AeroRichEdit _control;
        private readonly EventHandlerList _events = new EventHandlerList();
        private readonly List<EditControlDataTip> _tokens = new List<EditControlDataTip>();

        private DataTipForm _currentForm;
        private RichTextRange _currentRange;
        private Rectangle _currentDots;
        
        private RepeatingHoverProvider _hover;

        public EditControlDataTipProvider(AeroRichEdit control)
        {
            _control = control;
            _control.TextChanged += OnTextChanged;

            DotColor = _control.ForeColor;
            
            _hover = new RepeatingHoverProvider(control);
            _hover.MouseHover += OnMouseHover;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public Boolean Enabled
        {
            get { return _hover.Enabled; }
            set
            {
                _hover.Enabled = value;

                if (!value)
                {
                    HideTip();
                }
            }
        }

        public Color DotColor
        {
            get;
            set;
        }

        [CLSCompliant(false)]
        public EditControlDataTip AddItem(RichTextRange range, DataTipItem item)
        {
            return AddItem(range.Start, range.End - range.Start, item);
        }

        public EditControlDataTip AddItem(int start, int length, DataTipItem item)
        {
            var result = new EditControlDataTip(start, length, item);
            _tokens.Add(result);

            return result;
        }

        public void RemoveItem(EditControlDataTip item)
        {
            _tokens.Remove(item);
        }

        public void ClearItems()
        {
            _tokens.Clear();
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _control.TextChanged -= OnTextChanged;

                HideTip();

                foreach (var item in _tokens)
                {
                    item.Item.Dispose();
                }
                
                _events.Dispose();
                _hover.Dispose();
            }
        }

        private DataTipForm CreateForm(DataTipItem item, Point location)
        {
            var form = new DataTipForm()
            {
                DataItems = { item },
                Location = _control.PointToScreen(location),
            };

            var screenBounds = Screen.GetBounds(_control);

            if (form.Bounds.Right > screenBounds.Right)
            {
                form.Left -= (form.Bounds.Right - screenBounds.Right);
            }

            if (form.Bounds.Bottom > screenBounds.Bottom)
            {
                form.Top -= (form.Bounds.Bottom - screenBounds.Bottom);
            }

            form.Disposed += delegate { HideTip(); };
            return form;
        }

        private void OnMouseHover(object sender, MouseEventArgs e)
        {
            if (!Enabled)
            {
                return;
            }

            var index = _control.GetCharIndexFromPosition(e.Location);
            var line = _control.GetLineFromCharIndex(index);
            var lineFirstChar = _control.GetFirstCharIndexFromLine(line);

            EditControlDataTip token = null;

            foreach (var t in _tokens)
            {
                if (t.Contains(index))
                {
                    token = t;
                    break;
                }
            }

            if ((token != null) && (token.Length > 0))
            {
                var location = _control.GetPositionFromCharIndex(index);

                var start = token.Start;
                var end = token.End;

                if ((_currentRange != null) &&
                    (_currentRange.Start == start) &&
                    (_currentRange.End == end))
                {
                    return;
                }

                HideTip();

                if (e.Y > _control.FontHeight + location.Y)
                {
                    return;
                }

                var range = _control.TextDocument.GetRange(start, end);

                if (range.IsSingleLine)
                {
                    if (e.X > range.Rectangle.Right)
                    {
                        return;
                    }
                }

                _currentRange = range;
                _currentDots = range.Rectangle;

                PaintDots();

                location.X = e.X;
                location.Y += _control.FontHeight;

                _currentForm = CreateForm(token.Item, location);
                _currentForm.Show(_control);
            }
        }

        private void PaintDots()
        {
            using (var g = _control.CreateGraphics())
            {
                using (var p = new Pen(DotColor, 1) { DashStyle = DashStyle.Dot })
                {
                    g.DrawRectangle(p, _currentDots);
                }
            }
        }

        private void OnTextChanged(object sender, EventArgs e)
        {
            HideTip();
        }

        private void HideTip()
        {
            if (_currentForm != null)
            {
                _currentForm.Dispose();
                _currentForm = null;
            }

            if (_currentRange != null)
            {
                _currentRange = null;

                _control.Invalidate();
                _control.Update();
            }
        }
    }
}